#include <stdio.h> // Для printf
#include <stdlib.h> // Для malloc
#include <string.h> // bzero и иже с ним
#include <getopt.h> // Для getopt (Сканирование аргументов)
#include <unistd.h> // Для close
#include <pthread.h> // Для потоков.
#include <arpa/inet.h> // send/recv
#include <time.h> // 

#include <network.h> //	
#include <server.h>  //(¯◡◡¯) 

#define LOGFILE "log"

void addClient(int, struct sockaddr_in);
void exitNow(char *);
void scanArgs(int argc, char *[]);
void networkInitialization();
void otherInitialization();
void listening();

/*									*/
struct sockaddr_in serveraddr;
int port = 2012;
int sockfd = 0;
int gamecount = 100;
int idGame = 0, idClient = 0;
FILE *logfile;
time_t howtime;
/*									*/
/*	Номер порта по умолчанию 2012
 				количество игр 100  */

void exitNow(char *msg){
	if (msg != NULL){
		printf("%s\n",msg);
	}
	if (sockfd > 0){ // Закрытие прослушивающего сокета.
		close(sockfd);
	}
	if (clients != NULL){
		int i;
		for (i = 0; i < gamecount*2; i++){ // Закрываем все подклюения
			if (clients[i].sockfd > 0){
				close(clients[i].sockfd);
			}
		}
	}
	if (logfile != NULL){
		fclose(logfile); 
	}
	exit(0);
}

void scanArgs(int argc, char *argv[]){
	struct option long_options[] = {
					 {"port", required_argument, NULL, 'p'},
					 {"gamecount", required_argument, NULL, 'g'},
  					 {0, 0, 0, 0} 
				     };
	char arg; 

	while ((arg = getopt_long(argc, argv, "p:g:", long_options, NULL)) != -1){
		switch (arg){
			case 'p':{
				port = atoi(optarg);
				if ((1024 > port) || (port > 65536))
					exitNow("Некорректный порт. Допустимые значения: 1024-65536.");
				break;
			}
			case 'g':{
				gamecount = atoi(optarg);
				if ((1 > gamecount) || (gamecount > 256))
					exitNow("Некорректное количество игр. Допустимые значения: 1-256.");
				break;
			}
			default:
				exitNow("[-p|--port Номер порта] [-g|--gamecount Количество игр]");
		}
	}
	printf("Порт: %d.\tКоличество игр: %d\n", port, gamecount);
}

void networkInitialization(){
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		exitNow("Ошибка при создании сокета.");
	}
	    
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(port);
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
   
	if (bind(sockfd, (struct sockaddr*) &serveraddr, sizeof(serveraddr)) < 0)
		exitNow("Ошибка привязки сокета.");

	if (listen(sockfd, gamecount * 2) < 0){
		exitNow("Ошибка прослушмивания сокета.");
	}
}

void otherInitialization(){	
	/* Если подумать, то в резеврировании памяти нет особого смысла.
	 * Структуру под клиента, можно создавать во время подключения.
	 * И там же делать проверку на выделение памяти, в случае чего 
	 * Отправлять клиенту, о перегруженности сервера.
	 * Структуру под игры создавать, при создании игры естесственно.
	 * Проверку на нехватку памяти делать там же, в случае чего
	 * Отправлять клиенту сообщение, о перегруженности сервера.	
	*/
	if ((clients = malloc(sizeof(struct client) * gamecount * 2)) == NULL){ // Резервируем память под клиентов
		exitNow("Ошибка. Не достаточно памяти (clients)");
	}

	if ((games = malloc(sizeof(struct game) * gamecount)) == NULL){ // Под игры
		exitNow("Ошибка. Не достаточно памяти (games)");
	}

	int i;
	for (i = 0; i < gamecount; i++){
		games[i].black = games[i].white = NULL;
		games[i].play = 0;
		clients[i].sockfd = clients[gamecount+i].sockfd = -1;
	}
}

void listening(){ 
	time(&howtime);
	fprintf(logfile, "%sServer started. Port = %d, gamecount = %d.\n", asctime(localtime(&howtime)), port, gamecount);
	fflush(logfile);
	int newSockfd;
	struct sockaddr_in clientaddr;
	socklen_t length = sizeof(clientaddr);
	
	while((newSockfd = accept(sockfd, (struct sockaddr *) &clientaddr, &length)) > 0){
		addClient(newSockfd, clientaddr);
	}
}

void addClient(int clientSockfd, struct sockaddr_in clientAddr){ 
	int i;
	for (i = 0; i < gamecount * 2; i++){ // Ищем свободный сокет для вошедшего клиента.
		if (clients[i].sockfd == -1) break;
	}
	if (i == gamecount * 2 - 1) {
		printf("Какой-то клиент не смог подключиться из-за нехватки мест на сервере.\n");
		sending(clientSockfd, MAXCONNECT, NULL);
		return;
	}
	bzero(&clients[i], sizeof(struct client)); 
	clients[i].id = idClient;
	idClient++;
	clients[i].sockfd = clientSockfd;
	strncpy(clients[i].ip, inet_ntoa(clientAddr.sin_addr),sizeof(clients[i].ip));
	pthread_create(&clients[i].thread, NULL, lobby, (void *) &clients[i]);
	printf("Подключен клиент №%d, ID - %d IP - %s\n", i+1,  clients[i].id, clients[i].ip);
}

void openFile(char *path){
	int resize = 0;
	char *temp = strrchr(path,  '/');
	resize = strlen(path) - strlen(temp);
	path[resize+1] = '\0';
	strcat(path, LOGFILE);

	logfile = fopen(path, "a");
	if(logfile == NULL){
		exitNow("Не удалось открыть файл с журналом.");
	}
}

int main(int argc, char *argv[])
{	
	openFile(argv[0]);	
	scanArgs(argc, argv);
	otherInitialization();
	networkInitialization();
	printf("Сервер запущен\n");
	listening();
	printf("Сервер отключен\n");
	return 0; 
}
